package com.jlt.baidu;

import static org.opencv.imgproc.Imgproc.CHAIN_APPROX_SIMPLE;
import static org.opencv.imgproc.Imgproc.RETR_CCOMP;
import static org.opencv.imgproc.Imgproc.approxPolyDP;
import static org.opencv.imgproc.Imgproc.arcLength;
import static org.opencv.imgproc.Imgproc.contourArea;
import static org.opencv.imgproc.Imgproc.minAreaRect;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.TreeMap;

import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.leptonica.PIX;
import org.bytedeco.leptonica.global.lept;
import org.bytedeco.tesseract.TessBaseAPI;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.QRCodeDetector;

import com.jlt.baidu.utils.Opencv420Util;
import com.jlt.ocr.tesseract.OcrRectangle;

/**
 * 测试图片识别
 * 
 * @author Ives.Chen
 *
 */
public class TesseractTestUtil {


    public static void main(String[] args) throws Exception {
        TessBaseAPI api = new TessBaseAPI();
        init(api);
        imageOcr(api);
        // ocrtest(api);
        // qrcodeOcr(api);
    }

    public static void ocrtest(TessBaseAPI api) {
        String fileName = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_table-1.png";
        String fileNameGray = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_table-1_extends.png";
        Mat mat = Opencv420Util.imagerLoad(fileName);
        Mat dst = new Mat();
        int top = (int) (mat.rows() * 0.02);
        int bottom = (int) (mat.rows() * 0.02);
        int left = (int) (mat.cols() * 0.02);
        int right = (int) (mat.cols() * 0.02);
        Core.copyMakeBorder(mat, dst, top, bottom, left, right, Core.BORDER_REPLICATE);
        Opencv420Util.imageSave(fileNameGray, dst);
    }

    // 二维码/条形码识别
    public static void qrcodeOcr(TessBaseAPI api) {
        String fileName = "F:\\workSpace\\OCR\\qrcode\\qrcode.png";
        String fileNameGray = "F:\\workSpace\\OCR\\qrcode\\qrcod_gray.png";
        String fileNameCann = "F:\\workSpace\\OCR\\qrcode\\qrcod_canny.png";
        String fileNamethred = "F:\\workSpace\\OCR\\qrcode\\qrcod_thred.png";
        String fileNamedialet = "F:\\workSpace\\OCR\\qrcode\\qrcod_dialet.png";
        String fileNameline = "F:\\workSpace\\OCR\\qrcode\\qrcod_line.png";
        String fileNameline1 = "F:\\workSpace\\OCR\\qrcode\\qrcod_line1.png";

        // 1.读取原始文件
        Mat mat = Opencv420Util.imagerLoad(fileName);
        Mat gray = Opencv420Util.imageGray(mat.clone());
        Mat mat3 = Opencv420Util.gaussianBlur(gray.clone());
        Opencv420Util.imageSave(fileNameGray, mat3);
        Mat sobel = Opencv420Util.imageCanny(mat3, 120, 200);
        Opencv420Util.imageSave(fileNameCann, sobel);
        // 图片二值化，去除背景，增强图片
        Mat mat4 = Opencv420Util.thresholdWhiteGround(sobel.clone(), 100);
        Opencv420Util.imageSave(fileNamethred, mat4);
        Mat dilateElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 2));
        Mat encodeElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 2));
        Mat dilate = mat4.clone();
        Imgproc.morphologyEx(mat4, dilate, Imgproc.MORPH_DILATE, dilateElement);
        Mat encode = dilate.clone();
        Imgproc.morphologyEx(dilate, encode, Imgproc.MORPH_ERODE, encodeElement);

        Opencv420Util.imageSave(fileNamedialet, encode);
        // 查找二维码正方形区域域轮廓
        List<RotatedRect> rects = new ArrayList<>();
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = encode.clone();
        // 所有轮廓层级
        Imgproc.findContours(encode, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE, new Point(0, 0));
        for (int i = 0; i < contours.size(); i++) {
            // 计算当前轮廓的面积
            double area = contourArea(contours.get(i));
            // 面积小于1000的全部筛选掉
            if (area < 1000) {
                continue;
            }
            // 轮廓近似，作用较小，approxPolyDP函数有待研究
            MatOfPoint2f matOfPoint2f = new MatOfPoint2f(contours.get(i).toArray());
            double epsilon = 0.002 * arcLength(matOfPoint2f, true);
            approxPolyDP(matOfPoint2f, matOfPoint2f, epsilon, true);

            // 找到最小矩形，该矩形可能有方向
            RotatedRect rect = minAreaRect(matOfPoint2f);

            // 筛选宽，高差不多
            if (rect.boundingRect().height > rect.boundingRect().width - 30
                    || rect.boundingRect().height < rect.boundingRect().width - 30) {
                // 符合条件的rect添加到rects集合中
                rects.add(rect);
            }

        }
        // 画出轮廓线
        Mat lsin = mat.clone();
        for (RotatedRect rect : rects) {
            Point[] rectPoint = new Point[4];
            rect.points(rectPoint);
            for (int j = 0; j <= 3; j++) {
                Imgproc.line(lsin, rectPoint[j], rectPoint[(j + 1) % 4], new Scalar(0, 255, 0), 1);
            }
        }
        Opencv420Util.imageSave(fileNameline, lsin);

        List<MatOfPoint> contours1 = new ArrayList<>();
        Mat hierarchy1 = encode.clone();
        // 最外层轮廓层级
        Imgproc.findContours(encode, contours1, hierarchy1, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE, new Point(0, 0));
        RotatedRect minQr = null;
        for (int i = 0; i < contours1.size(); i++) {
            // 轮廓近似，作用较小，approxPolyDP函数有待研究
            MatOfPoint2f matOfPoint2f = new MatOfPoint2f(contours.get(i).toArray());
            double epsilon = 0.002 * arcLength(matOfPoint2f, true);
            approxPolyDP(matOfPoint2f, matOfPoint2f, epsilon, true);

            // 找到外层最小矩形，该矩形可能有方向
            RotatedRect rect = minAreaRect(matOfPoint2f);
            if (minQr != null && minQr.boundingRect().width < rect.boundingRect().width) {
                minQr = rect;
            } else {
                minQr = rect;
            }
        }
        Point[] rectPoint1 = new Point[4];
        Mat ssasa = mat.clone();
        minQr.points(rectPoint1);
        for (int j = 0; j <= 3; j++) {
            Imgproc.line(ssasa, rectPoint1[j], rectPoint1[(j + 1) % 4], new Scalar(0, 255, 0), 1);
        }
        Opencv420Util.imageSave(fileNameline1, ssasa);

        QRCodeDetector qr = new QRCodeDetector();
        System.out.println("原始文件：" + qr.detectAndDecode(mat));
        System.out.println("原始文件：" + qr.decode(mat, ssasa));

    }


    // 图片全文识别
    public static void imageOcr(TessBaseAPI api) throws IOException {
        // String fileName = "F:\\workSpace\\OCR\\upload\\603e17f8fac740858b36509666901252.png";
        String fileName = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1.png";
        String fileNameLine = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_line.png";
        String fileGray = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_gray.png";
        String fileWhite = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_white.png";
        String fileBlack_average = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_white_aver.png";
        String fileBlack_gras = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_white_gras.png";
        String fileBlack_gras1 = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_white_gras1.png";
        String fileBlack_mid = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_white_mid.png";
        String fileBlack_canny = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_Canny.png";
        String fileBlack_rangion = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_rangion.png";
        String fileBlack_sobel = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_sobel.png";
        String fileBlack_dilate = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_dilate.png";
        String fileBlack_dilate1 = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_dilate1.png";
        String fileBlack_erode = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_erode.png";
        String fileBlack_red = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_red.png";
        String fileBlack_borde = "F:\\workSpace\\OCR\\suyue\\predict\\2019-12-12_1_borde.png";

        // 实现目标：非定向识别，全文字识别结果汇总
        // 1.读取原始文件
        Mat mat = Opencv420Util.imagerLoad(fileName);
        // 提取图片内容的最外层矩形区域，形成读取的区域
        Mat first = Opencv420Util.imageGray(mat);
        // 加粗表格的边框线条
        // 去除红色印章 单独开一个识别印章的功能
        // Mat first = Opencv420Util.cancleRedAndblueConten(mat);
        // Opencv420Util.imageSave(fileBlack_red, first);
        // 旋转矫正图片位置至正面垂直 图片旋转无法智能处理内容是否正面，改为业务控制，要求上传的图片内容要正
        // Mat mat23213 = OpencvRotateUtil.handleMatRange(mat);
        // Opencv420Util.imageSave(fileBlack_rangion, mat23213);
        // 高斯滤波降噪

        // 边缘检测
        // Mat sobel = Opencv420Util.Sobel(mat3);
        Mat sobel = Opencv420Util.imageCanny(first.clone(), 120, 200);
        Opencv420Util.imageSave(fileBlack_sobel, sobel);

        // 中值滤波
        Mat mat4 = Opencv420Util.medianBlur(sobel.clone());
        Opencv420Util.imageSave(fileBlack_gras1, mat4);
        // 图片二值化，去除背景，增强图片
        Mat mat1 = Opencv420Util.thresholdWhiteGround(mat4.clone(), 100);
        // 去除边框的线条干扰
        // int cutNum = 10;
        // Mat submat = mat1.submat(cutNum, mat1.rows() - cutNum, cutNum, mat1.cols() - cutNum);
        // 写入临时文件，读取后再删除，避免直接添加边框图片内容又恢复的问题
        // 减去的边缘再填充回来，避免生成的坐标变位
        // Mat mas = new Mat();
        // Core.copyMakeBorder(submat, mas, cutNum, cutNum, cutNum, cutNum, Core.BORDER_CONSTANT, new Scalar(0, 0, 0));
        // Opencv420Util.imageSave(fileBlack_borde, mas);

        // 像素点加粗到sizeW*sizeH，让矩形闭合
        // Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
        // Imgproc.morphologyEx(mas, mas, Imgproc.MORPH_GRADIENT, element);
        // Opencv420Util.imageSave(fileWhite, mas);

        Mat mat3 = Opencv420Util.gaussianBlur(mat1.clone());
        Opencv420Util.imageSave(fileBlack_gras, mat3);
        // 腐蚀，膨胀 核心代码
        // 腐蚀的意义在于去除一些干扰线（细小），膨胀出文字区域，用于标记文字块
        // 要想文字一个个读取，腐蚀要越大，一块块读取，腐蚀应该膨胀的一半，三分一
        Mat dilateElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(20, 10));
        Mat erodeElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(10, 20));
        Mat dilate1Element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(20, 10));
        Mat erode1Element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(15, 30));
        /*
         * 开运算MORPH_OPEN：先腐蚀再膨胀，用来消除小物体
         * 
         * 闭运算MORPH_CLOSE：先膨胀再腐蚀，用于排除小型黑洞
         * 
         * 形态学梯度MORPH_GRADIENT：就是膨胀图与俯视图之差，用于保留物体的边缘轮廓。
         * 
         * 顶帽MORPH_TOPHAT：原图像与开运算图之差，用于分离比邻近点亮一些的斑块。
         * 
         * 黑帽MORPH_BLACKHAT：闭运算与原图像之差，用于分离比邻近点暗一些的斑块。
         * 
         * 腐蚀 MORPH_ERODE 膨胀 MORPH_DILATE
         */
        Mat dilate = mat3.clone();
        // Imgproc.morphologyEx(mat1, erode1, Imgproc.MORPH_CLOSE, dilateElement);
        // 膨胀
        Imgproc.dilate(mat3, dilate, dilateElement);
        Opencv420Util.imageSave(fileBlack_dilate, dilate);
        // 腐蚀
        Mat erode = dilate.clone();
        Imgproc.erode(dilate, erode, erodeElement);
        Opencv420Util.imageSave(fileBlack_erode, erode);
        // 膨胀
        Mat dilate1 = erode.clone();
        Imgproc.dilate(erode, dilate1, dilate1Element);
        Opencv420Util.imageSave(fileBlack_dilate1, dilate1);
        // 腐蚀
        Mat erode1 = dilate1.clone();
        Imgproc.erode(dilate1, erode, erode1Element);
        Opencv420Util.imageSave(fileBlack_erode, erode1);

        // 查找文字区域轮廓
        List<RotatedRect> rects = new ArrayList<>();
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = erode1.clone();
        Imgproc.findContours(erode1, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE, new Point(0, 0));
        for (int i = 0; i < contours.size(); i++) {
            // 计算当前轮廓的面积
            double area = contourArea(contours.get(i));
            // 面积小于1000的全部筛选掉
            if (area < 1000) {
                continue;
            }
            // 轮廓近似，作用较小，approxPolyDP函数有待研究
            MatOfPoint2f matOfPoint2f = new MatOfPoint2f(contours.get(i).toArray());
            double epsilon = 0.002 * arcLength(matOfPoint2f, true);
            approxPolyDP(matOfPoint2f, matOfPoint2f, epsilon, true);

            // 找到最小矩形，该矩形可能有方向
            RotatedRect rect = minAreaRect(matOfPoint2f);

            // 筛选那些太细的矩形，留下扁的 舍去高是宽的2倍
            if (rect.boundingRect().height > rect.boundingRect().width * 2) {
                continue;
            }
            // 舍去边缘的干扰矩形
            // 顶部5
            if (rect.boundingRect().y < 5) {
                continue;
            }
            // 底部20
            if (rect.boundingRect().y >= mat.rows() - 20) {
                continue;
            }
            // 左侧5
            if (rect.boundingRect().x < 10) {
                continue;
            }
            // 右侧10
            if (rect.boundingRect().x > mat.cols() - 10) {
                continue;
            }
            // 去除几乎覆盖整个图片的文本框
            if (rect.boundingRect().height >= mat.rows() - 100) {
                continue;
            }
            // 符合条件的rect添加到rects集合中
            rects.add(rect);
        }
        // 画出轮廓线
        List<OcrRectangle> reaList = new ArrayList<>();
        for (RotatedRect rect : rects) {
            Point[] rectPoint = new Point[4];
            rect.points(rectPoint);
            for (int j = 0; j <= 3; j++) {
                Imgproc.line(mat, rectPoint[j], rectPoint[(j + 1) % 4], new Scalar(0, 255, 0), 2);
            }
            int top = rect.boundingRect().y;
            int left = rect.boundingRect().x;
            int width = rect.boundingRect().width;;
            int height = rect.boundingRect().height;
            OcrRectangle range = new OcrRectangle(left, top, width, height);
            reaList.add(range);
        }
        Opencv420Util.imageSave(fileNameLine, mat);
        // 对所有矩形块进行分行筛选
        TreeMap<Integer, List<OcrRectangle>> rows = new TreeMap<>();
        for (OcrRectangle tmp : reaList) {
            boolean flag = true;
            for (Integer top : rows.keySet()) {
                // 高度相差20 判断为同一行
                if (Math.abs(top - tmp.getTop()) <= 25) {
                    rows.get(top).add(tmp);
                    flag = false;
                    break;
                }
            }
            if (flag) {
                List<OcrRectangle> row = new ArrayList<>();
                row.add(tmp);
                rows.put(tmp.getTop(), row);
            }
        }
        // ocr识别
        PIX image = lept.pixRead(fileName);
        api.SetImage(image);
        for (List<OcrRectangle> row : rows.values()) {
            StringBuilder ocrResult = new StringBuilder();
            // 排序 从左向右读取
            row.sort(Comparator.comparingInt(OcrRectangle::getLeft));
            for (OcrRectangle range : row) {
                api.SetRectangle(range.getLeft(), range.getTop(), range.getWidth(), range.getHeight());
                api.Recognize(null);
                BytePointer outText = api.GetUTF8Text();
                if (outText != null) {
                    String text = outText.getString().trim().replace("\n", "");
                    outText.deallocate();
                    ocrResult.append(text).append("\t");
                }
            }
            // 横线会被解析成 一 舍去
            if (ocrResult.toString().contains("一")) {
                continue;
            }
            System.out.println(ocrResult.toString());
        }
        api.End();
        lept.pixDestroy(image);
        api.close();
    }

    public static void init(TessBaseAPI api) {
        String opencv420 = "F:\\workSpace\\JLTMdmWorkSpace\\jlt-baidu\\src\\main\\resources\\opencv\\java\\x64\\opencv_java420.dll";
        System.load(opencv420);
        String tesseractPath = "F:\\workSpace\\JLTMdmWorkSpace\\jlt-baidu\\src\\main\\resources\\tessdata\\";
        if (api.Init(tesseractPath, "chi_sim+eng") != 0) {
            throw new RuntimeException("tesseract init fail");
        }
    }
}
